package synthlab.internal.modules;

import synthlab.api.Module;
import synthlab.api.Port;
import synthlab.api.Scheduler;
import synthlab.api.Port.ValueUnit;
import synthlab.internal.BasicModule;
import synthlab.internal.BasicPort;


/**
 * Concrete module class of VCO
 * 4 input signal control manually in UI or externally by connecting signal ,Active for switch this module ON/OFF, 
 * Constant for K, 
 * Frequency for fm
 * Shape for change different wave form
 * 1 output signal
 * @author Dayou
 * */
public class ModuleVCO extends BasicModule
{
  

  public static final double SHAPE_SAWTOOTH = 0.75;
  public static final double SHAPE_TRIANGLE = 0.50;
  public static final double SHAPE_SINE     = 0.25;
  public static final double SHAPE_PULSE    = 0.00;

  private int                frameRate_;
  private double             initialFrequency_;
  private double       currentPositionInPeriod_;
  
  /**
   * Constructor of VCO initial all ports E/S, initial name of module
   * initial base Data: frame rate, frequency
   * */
  public ModuleVCO()
  {
    super("VCO");

    addInput(new BasicPort("iFrequency", 0, Port.ValueType.CONTINUOUS,
        Port.ValueUnit.VOLT, new Port.ValueRange(0, 8), "Frequency modulation following the 1v/octave convention"));

    addInput(new BasicPort("iConstant", 4, Port.ValueType.CONTINUOUS,
        Port.ValueUnit.VOLT, new Port.ValueRange(0, 8), "Frequency modulation constant following the 1v/octave convention"));

    addInput(new BasicPort("iShape", SHAPE_SINE, Port.ValueType.WAVE_SHAPE,
        Port.ValueUnit.AMPLITUDE, new Port.ValueRange(0, 1, 4), "Shape of the generated sound wave"));
    
    addInput(new BasicPort("iActive", 1, Port.ValueType.SWITCH,
        ValueUnit.AMPLITUDE, new Port.ValueRange(-1, 1), "Active control signal"));

    addOutput(new BasicPort("oSignal", 0, Port.ValueType.CONTINUOUS,
        Port.ValueUnit.AMPLITUDE, new Port.ValueRange(-1, 1), "Sound wave generated by the VCO"));

  
    frameRate_ = 44100;
    initialFrequency_ = 32.7; // Octave:0, Note:Do , with Hz
    
    currentPositionInPeriod_ = 0.0;
  }
  

  /**
   * compute method 
   * */

  @Override
  public void compute()
  {
    synchronized (getInput("iShape"))
    {
      synchronized (getInput("iFrequency"))
      {
        synchronized (getInput("iConstant"))
        {
          synchronized (getInput("iActive"))
          {
            synchronized (getOutput("oSignal"))
            {
              getInput("iShape").getValues().clear();
              getInput("iFrequency").getValues().clear();
              getInput("iConstant").getValues().clear();
              getInput("iActive").getValues().clear();
              getOutput("oSignal").getValues().clear();
  
              
              for (int i = 0; i < Scheduler.SamplingBufferSize; ++i)
              {
                double out = 0;
                double ishape = getInput("iShape").getValues().getDouble();
                double ifreq = getInput("iFrequency").getValues().getDouble();
                double iconst = getInput("iConstant").getValues().getDouble();
  
               // double positionInPeriod = (double) frameCount_
                 //   / (double) frameRate_;
                double frequency = Math.pow(2, ifreq + iconst)
                    * initialFrequency_;
  
                double framePerPeriod_ = (double) frameRate_ / frequency;
                double currentPositionInPercent = currentPositionInPeriod_/framePerPeriod_;
  
                if (ishape >= SHAPE_SAWTOOTH && ishape <1)
                {
                  out = 2*currentPositionInPeriod_ / framePerPeriod_ - 1.0;
                }
                else if (ishape >= SHAPE_TRIANGLE && ishape < SHAPE_SAWTOOTH)
                {
                  if (currentPositionInPercent < 0.25)
                    out = 4.0 * currentPositionInPercent;
                  else if (currentPositionInPercent < 0.75)
                    out = 2.0 - 4.0 * currentPositionInPercent;
                  else
                    out = 4.0 * currentPositionInPercent - 4.0;
                }
                else if (ishape >= SHAPE_SINE && ishape < SHAPE_TRIANGLE)
                {
                  out = Math.sin(currentPositionInPercent * 2. * Math.PI);
                }
                else if (ishape >= SHAPE_PULSE && ishape < SHAPE_SINE)
                {
                  out = currentPositionInPercent > 0.5 ? 1.0 : -1.0;
                }
                else
               {
                  assert (false);
                }
                
                double active = getInput("iActive").getValues().getDouble();
                if(active > 0)
                  getOutput("oSignal").getValues().putDouble(out);
                else
                  getOutput("oSignal").getValues().putDouble(0);
                  
                
                currentPositionInPeriod_ +=  1.0 ;
                if(currentPositionInPeriod_ >= framePerPeriod_)
                  currentPositionInPeriod_ -= framePerPeriod_;
              }
            }
          }
        }
      }
    }
  }
  
  
  /**
   * Method for create a new module of VCO
   * */
  
  @Override
  public Module clone() throws CloneNotSupportedException
  {
    return new ModuleVCO();
  }

}
